package com.dylibso.chicory.compiler.internal;

import com.dylibso.chicory.wasm.types.OpCode;
import java.util.EnumMap;
import java.util.Map;
import java.util.Optional;

enum CompilerOpCode {
    LABEL,
    DROP_KEEP,
    TRAP,
    GOTO,
    IFEQ,
    IFNE,
    SWITCH,
    RETURN(OpCode.RETURN),
    CALL(OpCode.CALL),
    CALL_INDIRECT(OpCode.CALL_INDIRECT),
    DROP(OpCode.DROP),
    SELECT(OpCode.SELECT),
    LOCAL_GET(OpCode.LOCAL_GET),
    LOCAL_SET(OpCode.LOCAL_SET),
    LOCAL_TEE(OpCode.LOCAL_TEE),
    GLOBAL_GET(OpCode.GLOBAL_GET),
    GLOBAL_SET(OpCode.GLOBAL_SET),
    TABLE_GET(OpCode.TABLE_GET),
    TABLE_SET(OpCode.TABLE_SET),
    I32_LOAD(OpCode.I32_LOAD),
    I64_LOAD(OpCode.I64_LOAD),
    F32_LOAD(OpCode.F32_LOAD),
    F64_LOAD(OpCode.F64_LOAD),
    I32_LOAD8_S(OpCode.I32_LOAD8_S),
    I32_LOAD8_U(OpCode.I32_LOAD8_U),
    I32_LOAD16_S(OpCode.I32_LOAD16_S),
    I32_LOAD16_U(OpCode.I32_LOAD16_U),
    I64_LOAD8_S(OpCode.I64_LOAD8_S),
    I64_LOAD8_U(OpCode.I64_LOAD8_U),
    I64_LOAD16_S(OpCode.I64_LOAD16_S),
    I64_LOAD16_U(OpCode.I64_LOAD16_U),
    I64_LOAD32_S(OpCode.I64_LOAD32_S),
    I64_LOAD32_U(OpCode.I64_LOAD32_U),
    I32_STORE(OpCode.I32_STORE),
    I64_STORE(OpCode.I64_STORE),
    F32_STORE(OpCode.F32_STORE),
    F64_STORE(OpCode.F64_STORE),
    I32_STORE8(OpCode.I32_STORE8),
    I32_STORE16(OpCode.I32_STORE16),
    I64_STORE8(OpCode.I64_STORE8),
    I64_STORE16(OpCode.I64_STORE16),
    I64_STORE32(OpCode.I64_STORE32),
    MEMORY_SIZE(OpCode.MEMORY_SIZE),
    MEMORY_GROW(OpCode.MEMORY_GROW),
    I32_CONST(OpCode.I32_CONST),
    I64_CONST(OpCode.I64_CONST),
    F32_CONST(OpCode.F32_CONST),
    F64_CONST(OpCode.F64_CONST),
    I32_EQZ(OpCode.I32_EQZ),
    I32_EQ(OpCode.I32_EQ),
    I32_NE(OpCode.I32_NE),
    I32_LT_S(OpCode.I32_LT_S),
    I32_LT_U(OpCode.I32_LT_U),
    I32_GT_S(OpCode.I32_GT_S),
    I32_GT_U(OpCode.I32_GT_U),
    I32_LE_S(OpCode.I32_LE_S),
    I32_LE_U(OpCode.I32_LE_U),
    I32_GE_S(OpCode.I32_GE_S),
    I32_GE_U(OpCode.I32_GE_U),
    I64_EQZ(OpCode.I64_EQZ),
    I64_EQ(OpCode.I64_EQ),
    I64_NE(OpCode.I64_NE),
    I64_LT_S(OpCode.I64_LT_S),
    I64_LT_U(OpCode.I64_LT_U),
    I64_GT_S(OpCode.I64_GT_S),
    I64_GT_U(OpCode.I64_GT_U),
    I64_LE_S(OpCode.I64_LE_S),
    I64_LE_U(OpCode.I64_LE_U),
    I64_GE_S(OpCode.I64_GE_S),
    I64_GE_U(OpCode.I64_GE_U),
    F32_EQ(OpCode.F32_EQ),
    F32_NE(OpCode.F32_NE),
    F32_LT(OpCode.F32_LT),
    F32_GT(OpCode.F32_GT),
    F32_LE(OpCode.F32_LE),
    F32_GE(OpCode.F32_GE),
    F64_EQ(OpCode.F64_EQ),
    F64_NE(OpCode.F64_NE),
    F64_LT(OpCode.F64_LT),
    F64_GT(OpCode.F64_GT),
    F64_LE(OpCode.F64_LE),
    F64_GE(OpCode.F64_GE),
    I32_CLZ(OpCode.I32_CLZ),
    I32_CTZ(OpCode.I32_CTZ),
    I32_POPCNT(OpCode.I32_POPCNT),
    I32_ADD(OpCode.I32_ADD),
    I32_SUB(OpCode.I32_SUB),
    I32_MUL(OpCode.I32_MUL),
    I32_DIV_S(OpCode.I32_DIV_S),
    I32_DIV_U(OpCode.I32_DIV_U),
    I32_REM_S(OpCode.I32_REM_S),
    I32_REM_U(OpCode.I32_REM_U),
    I32_AND(OpCode.I32_AND),
    I32_OR(OpCode.I32_OR),
    I32_XOR(OpCode.I32_XOR),
    I32_SHL(OpCode.I32_SHL),
    I32_SHR_S(OpCode.I32_SHR_S),
    I32_SHR_U(OpCode.I32_SHR_U),
    I32_ROTL(OpCode.I32_ROTL),
    I32_ROTR(OpCode.I32_ROTR),
    I64_CLZ(OpCode.I64_CLZ),
    I64_CTZ(OpCode.I64_CTZ),
    I64_POPCNT(OpCode.I64_POPCNT),
    I64_ADD(OpCode.I64_ADD),
    I64_SUB(OpCode.I64_SUB),
    I64_MUL(OpCode.I64_MUL),
    I64_DIV_S(OpCode.I64_DIV_S),
    I64_DIV_U(OpCode.I64_DIV_U),
    I64_REM_S(OpCode.I64_REM_S),
    I64_REM_U(OpCode.I64_REM_U),
    I64_AND(OpCode.I64_AND),
    I64_OR(OpCode.I64_OR),
    I64_XOR(OpCode.I64_XOR),
    I64_SHL(OpCode.I64_SHL),
    I64_SHR_S(OpCode.I64_SHR_S),
    I64_SHR_U(OpCode.I64_SHR_U),
    I64_ROTL(OpCode.I64_ROTL),
    I64_ROTR(OpCode.I64_ROTR),
    F32_ABS(OpCode.F32_ABS),
    F32_NEG(OpCode.F32_NEG),
    F32_CEIL(OpCode.F32_CEIL),
    F32_FLOOR(OpCode.F32_FLOOR),
    F32_TRUNC(OpCode.F32_TRUNC),
    F32_NEAREST(OpCode.F32_NEAREST),
    F32_SQRT(OpCode.F32_SQRT),
    F32_ADD(OpCode.F32_ADD),
    F32_SUB(OpCode.F32_SUB),
    F32_MUL(OpCode.F32_MUL),
    F32_DIV(OpCode.F32_DIV),
    F32_MIN(OpCode.F32_MIN),
    F32_MAX(OpCode.F32_MAX),
    F32_COPYSIGN(OpCode.F32_COPYSIGN),
    F64_ABS(OpCode.F64_ABS),
    F64_NEG(OpCode.F64_NEG),
    F64_CEIL(OpCode.F64_CEIL),
    F64_FLOOR(OpCode.F64_FLOOR),
    F64_TRUNC(OpCode.F64_TRUNC),
    F64_NEAREST(OpCode.F64_NEAREST),
    F64_SQRT(OpCode.F64_SQRT),
    F64_ADD(OpCode.F64_ADD),
    F64_SUB(OpCode.F64_SUB),
    F64_MUL(OpCode.F64_MUL),
    F64_DIV(OpCode.F64_DIV),
    F64_MIN(OpCode.F64_MIN),
    F64_MAX(OpCode.F64_MAX),
    F64_COPYSIGN(OpCode.F64_COPYSIGN),
    I32_WRAP_I64(OpCode.I32_WRAP_I64),
    I32_TRUNC_F32_S(OpCode.I32_TRUNC_F32_S),
    I32_TRUNC_F32_U(OpCode.I32_TRUNC_F32_U),
    I32_TRUNC_F64_S(OpCode.I32_TRUNC_F64_S),
    I32_TRUNC_F64_U(OpCode.I32_TRUNC_F64_U),
    I64_EXTEND_I32_S(OpCode.I64_EXTEND_I32_S),
    I64_EXTEND_I32_U(OpCode.I64_EXTEND_I32_U),
    I64_TRUNC_F32_S(OpCode.I64_TRUNC_F32_S),
    I64_TRUNC_F32_U(OpCode.I64_TRUNC_F32_U),
    I64_TRUNC_F64_S(OpCode.I64_TRUNC_F64_S),
    I64_TRUNC_F64_U(OpCode.I64_TRUNC_F64_U),
    F32_CONVERT_I32_S(OpCode.F32_CONVERT_I32_S),
    F32_CONVERT_I32_U(OpCode.F32_CONVERT_I32_U),
    F32_CONVERT_I64_S(OpCode.F32_CONVERT_I64_S),
    F32_CONVERT_I64_U(OpCode.F32_CONVERT_I64_U),
    F32_DEMOTE_F64(OpCode.F32_DEMOTE_F64),
    F64_CONVERT_I32_S(OpCode.F64_CONVERT_I32_S),
    F64_CONVERT_I32_U(OpCode.F64_CONVERT_I32_U),
    F64_CONVERT_I64_S(OpCode.F64_CONVERT_I64_S),
    F64_CONVERT_I64_U(OpCode.F64_CONVERT_I64_U),
    F64_PROMOTE_F32(OpCode.F64_PROMOTE_F32),
    I32_REINTERPRET_F32(OpCode.I32_REINTERPRET_F32),
    I64_REINTERPRET_F64(OpCode.I64_REINTERPRET_F64),
    F32_REINTERPRET_I32(OpCode.F32_REINTERPRET_I32),
    F64_REINTERPRET_I64(OpCode.F64_REINTERPRET_I64),
    I32_EXTEND_8_S(OpCode.I32_EXTEND_8_S),
    I32_EXTEND_16_S(OpCode.I32_EXTEND_16_S),
    I64_EXTEND_8_S(OpCode.I64_EXTEND_8_S),
    I64_EXTEND_16_S(OpCode.I64_EXTEND_16_S),
    I64_EXTEND_32_S(OpCode.I64_EXTEND_32_S),
    REF_NULL(OpCode.REF_NULL),
    REF_IS_NULL(OpCode.REF_IS_NULL),
    REF_FUNC(OpCode.REF_FUNC),
    I32_TRUNC_SAT_F32_S(OpCode.I32_TRUNC_SAT_F32_S),
    I32_TRUNC_SAT_F32_U(OpCode.I32_TRUNC_SAT_F32_U),
    I32_TRUNC_SAT_F64_S(OpCode.I32_TRUNC_SAT_F64_S),
    I32_TRUNC_SAT_F64_U(OpCode.I32_TRUNC_SAT_F64_U),
    I64_TRUNC_SAT_F32_S(OpCode.I64_TRUNC_SAT_F32_S),
    I64_TRUNC_SAT_F32_U(OpCode.I64_TRUNC_SAT_F32_U),
    I64_TRUNC_SAT_F64_S(OpCode.I64_TRUNC_SAT_F64_S),
    I64_TRUNC_SAT_F64_U(OpCode.I64_TRUNC_SAT_F64_U),
    MEMORY_INIT(OpCode.MEMORY_INIT),
    DATA_DROP(OpCode.DATA_DROP),
    MEMORY_COPY(OpCode.MEMORY_COPY),
    MEMORY_FILL(OpCode.MEMORY_FILL),
    TABLE_INIT(OpCode.TABLE_INIT),
    ELEM_DROP(OpCode.ELEM_DROP),
    TABLE_COPY(OpCode.TABLE_COPY),
    TABLE_GROW(OpCode.TABLE_GROW),
    TABLE_SIZE(OpCode.TABLE_SIZE),
    TABLE_FILL(OpCode.TABLE_FILL),
    THROW(OpCode.THROW),
    THROW_REF(OpCode.THROW_REF),
    TRY_CATCH_BLOCK(OpCode.TRY_TABLE),
    CATCH_START,
    CATCH_END,
    CATCH_UNBOX_PARAMS,
    CATCH_COMPARE_TAG,
    CATCH_REGISTER_EXCEPTION,
    ATOMIC_INT_LOAD(OpCode.I32_ATOMIC_LOAD),
    ATOMIC_INT_LOAD_BYTE(OpCode.I32_ATOMIC_LOAD8_U),
    ATOMIC_INT_LOAD_SHORT(OpCode.I32_ATOMIC_LOAD16_U),
    ATOMIC_LONG_LOAD(OpCode.I64_ATOMIC_LOAD),
    ATOMIC_LONG_LOAD_BYTE(OpCode.I64_ATOMIC_LOAD8_U),
    ATOMIC_LONG_LOAD_SHORT(OpCode.I64_ATOMIC_LOAD16_U),
    ATOMIC_LONG_LOAD_INT(OpCode.I64_ATOMIC_LOAD32_U),
    ATOMIC_INT_STORE(OpCode.I32_ATOMIC_STORE),
    ATOMIC_LONG_STORE(OpCode.I64_ATOMIC_STORE),
    ATOMIC_INT_STORE_BYTE(OpCode.I32_ATOMIC_STORE8),
    ATOMIC_INT_STORE_SHORT(OpCode.I32_ATOMIC_STORE16),
    ATOMIC_LONG_STORE_BYTE(OpCode.I64_ATOMIC_STORE8),
    ATOMIC_LONG_STORE_SHORT(OpCode.I64_ATOMIC_STORE16),
    ATOMIC_LONG_STORE_INT(OpCode.I64_ATOMIC_STORE32),
    // Atomic RMW (I32)
    ATOMIC_INT_RMW_ADD(OpCode.I32_ATOMIC_RMW_ADD),
    ATOMIC_INT_RMW_SUB(OpCode.I32_ATOMIC_RMW_SUB),
    ATOMIC_INT_RMW_AND(OpCode.I32_ATOMIC_RMW_AND),
    ATOMIC_INT_RMW_OR(OpCode.I32_ATOMIC_RMW_OR),
    ATOMIC_INT_RMW_XOR(OpCode.I32_ATOMIC_RMW_XOR),
    ATOMIC_INT_RMW_XCHG(OpCode.I32_ATOMIC_RMW_XCHG),
    ATOMIC_INT_RMW_CMPXCHG(OpCode.I32_ATOMIC_RMW_CMPXCHG),
    ATOMIC_INT_RMW8_ADD_U(OpCode.I32_ATOMIC_RMW8_ADD_U),
    ATOMIC_INT_RMW8_SUB_U(OpCode.I32_ATOMIC_RMW8_SUB_U),
    ATOMIC_INT_RMW8_AND_U(OpCode.I32_ATOMIC_RMW8_AND_U),
    ATOMIC_INT_RMW8_OR_U(OpCode.I32_ATOMIC_RMW8_OR_U),
    ATOMIC_INT_RMW8_XOR_U(OpCode.I32_ATOMIC_RMW8_XOR_U),
    ATOMIC_INT_RMW8_XCHG_U(OpCode.I32_ATOMIC_RMW8_XCHG_U),
    ATOMIC_INT_RMW8_CMPXCHG_U(OpCode.I32_ATOMIC_RMW8_CMPXCHG_U),
    ATOMIC_INT_RMW16_ADD_U(OpCode.I32_ATOMIC_RMW16_ADD_U),
    ATOMIC_INT_RMW16_SUB_U(OpCode.I32_ATOMIC_RMW16_SUB_U),
    ATOMIC_INT_RMW16_AND_U(OpCode.I32_ATOMIC_RMW16_AND_U),
    ATOMIC_INT_RMW16_OR_U(OpCode.I32_ATOMIC_RMW16_OR_U),
    ATOMIC_INT_RMW16_XOR_U(OpCode.I32_ATOMIC_RMW16_XOR_U),
    ATOMIC_INT_RMW16_XCHG_U(OpCode.I32_ATOMIC_RMW16_XCHG_U),
    ATOMIC_INT_RMW16_CMPXCHG_U(OpCode.I32_ATOMIC_RMW16_CMPXCHG_U),
    // Atomic RMW (I64)
    ATOMIC_LONG_RMW_ADD(OpCode.I64_ATOMIC_RMW_ADD),
    ATOMIC_LONG_RMW_SUB(OpCode.I64_ATOMIC_RMW_SUB),
    ATOMIC_LONG_RMW_AND(OpCode.I64_ATOMIC_RMW_AND),
    ATOMIC_LONG_RMW_OR(OpCode.I64_ATOMIC_RMW_OR),
    ATOMIC_LONG_RMW_XOR(OpCode.I64_ATOMIC_RMW_XOR),
    ATOMIC_LONG_RMW_XCHG(OpCode.I64_ATOMIC_RMW_XCHG),
    ATOMIC_LONG_RMW_CMPXCHG(OpCode.I64_ATOMIC_RMW_CMPXCHG),
    ATOMIC_LONG_RMW8_ADD_U(OpCode.I64_ATOMIC_RMW8_ADD_U),
    ATOMIC_LONG_RMW8_SUB_U(OpCode.I64_ATOMIC_RMW8_SUB_U),
    ATOMIC_LONG_RMW8_AND_U(OpCode.I64_ATOMIC_RMW8_AND_U),
    ATOMIC_LONG_RMW8_OR_U(OpCode.I64_ATOMIC_RMW8_OR_U),
    ATOMIC_LONG_RMW8_XOR_U(OpCode.I64_ATOMIC_RMW8_XOR_U),
    ATOMIC_LONG_RMW8_XCHG_U(OpCode.I64_ATOMIC_RMW8_XCHG_U),
    ATOMIC_LONG_RMW8_CMPXCHG_U(OpCode.I64_ATOMIC_RMW8_CMPXCHG_U),
    ATOMIC_LONG_RMW16_ADD_U(OpCode.I64_ATOMIC_RMW16_ADD_U),
    ATOMIC_LONG_RMW16_SUB_U(OpCode.I64_ATOMIC_RMW16_SUB_U),
    ATOMIC_LONG_RMW16_AND_U(OpCode.I64_ATOMIC_RMW16_AND_U),
    ATOMIC_LONG_RMW16_OR_U(OpCode.I64_ATOMIC_RMW16_OR_U),
    ATOMIC_LONG_RMW16_XOR_U(OpCode.I64_ATOMIC_RMW16_XOR_U),
    ATOMIC_LONG_RMW16_XCHG_U(OpCode.I64_ATOMIC_RMW16_XCHG_U),
    ATOMIC_LONG_RMW16_CMPXCHG_U(OpCode.I64_ATOMIC_RMW16_CMPXCHG_U),
    ATOMIC_LONG_RMW32_ADD_U(OpCode.I64_ATOMIC_RMW32_ADD_U),
    ATOMIC_LONG_RMW32_SUB_U(OpCode.I64_ATOMIC_RMW32_SUB_U),
    ATOMIC_LONG_RMW32_AND_U(OpCode.I64_ATOMIC_RMW32_AND_U),
    ATOMIC_LONG_RMW32_OR_U(OpCode.I64_ATOMIC_RMW32_OR_U),
    ATOMIC_LONG_RMW32_XOR_U(OpCode.I64_ATOMIC_RMW32_XOR_U),
    ATOMIC_LONG_RMW32_XCHG_U(OpCode.I64_ATOMIC_RMW32_XCHG_U),
    ATOMIC_LONG_RMW32_CMPXCHG_U(OpCode.I64_ATOMIC_RMW32_CMPXCHG_U),
    // Wait/Notify
    MEM_ATOMIC_WAIT32(OpCode.MEM_ATOMIC_WAIT32),
    MEM_ATOMIC_WAIT64(OpCode.MEM_ATOMIC_WAIT64),
    MEM_ATOMIC_NOTIFY(OpCode.MEM_ATOMIC_NOTIFY),
    ATOMIC_FENCE(OpCode.ATOMIC_FENCE),
    ;

    private final Optional<OpCode> opcode;

    private static final Map<OpCode, CompilerOpCode> OPCODES = new EnumMap<>(OpCode.class);

    static {
        for (CompilerOpCode v : values()) {
            v.opcode.ifPresent(opcode -> OPCODES.put(opcode, v));
        }
    }

    CompilerOpCode() {
        this.opcode = Optional.empty();
    }

    CompilerOpCode(OpCode opcode) {
        this.opcode = Optional.of(opcode);
    }

    public OpCode opcode() {
        return opcode.orElseThrow(() -> new IllegalStateException("No opcode for " + this));
    }

    public static CompilerOpCode of(OpCode opcode) {
        CompilerOpCode value = OPCODES.get(opcode);
        if (value == null) {
            throw new IllegalArgumentException("No compiler opcode for " + opcode);
        }
        return value;
    }
}
